using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using ValidationFramework.Extensions;
using System.ComponentModel;
using System.Linq;
using ValidationFramework.Reflection;
namespace ValidationFramework
{
	/// <summary>
	/// A collection of <see cref="Rule"/>s with a binding list as its backing store of.
	/// </summary>
#if (!SILVERLIGHT)
	[Serializable]
#endif
	public sealed class RuleCollection : IEnumerable<Rule>
	{

        private readonly List<Rule> ruleList;
		private readonly RuntimeTypeHandle targetTypeHandle;

		// --- Constructors

		/// <summary>
		/// Initialize a new instance of the <see cref="RuleCollection"/> class.
		/// </summary>
		internal RuleCollection(RuntimeTypeHandle targetType)
		{
			targetTypeHandle = targetType;
			ruleList = new List<Rule>();
		}

		// --- Properties

		public int Count
		{
			get
			{
				return ruleList.Count;
			}
		}


		// --- Methods

		/// <inheritdoc />
		/// <exception cref="ArgumentNullException"><paramref name="item"/> is null.</exception> 
		/// <exception cref="ArgumentException">An element with the same key already exists in the <see cref="AutoKeyDictionary{TKey,TItem}"/>.</exception>
        public void Add(Rule item)
		{
			Guard.ArgumentNotNull(item, "item");

			CheckForDuplicate(item, item.RuleSet);

			CheckCanBeAppliedToType(item);

			ruleList.Add(item);
		}

		/// <summary>
		/// Adds a new rule to the list and returns a reference to it for editing
		/// </summary>
		/// <param name="item"></param>
		/// <returns></returns>
		public Rule AddNew(Rule item)
		{
			Add(item);
			// TODO: This is not thread safe, but for now assume the last item in the list is the one we just added
			return ruleList[ruleList.Count - 1];
		}

		internal void Merge(RuleCollection ruleCollection)
		{
			foreach (var rule in ruleCollection)
			{
				if (!IsDuplicate(rule))
				{
					Add(rule);
				}
			}
		}

		/// <inheritdoc />
        public void Remove(Rule rule)
		{
			ruleList.Remove(rule);
		}

		/// <inheritdoc />
		public void Clear()
		{
			ruleList.Clear();
		}

        private void CheckForDuplicate(Rule rule, string ruleSet)
		{
			if (IsDuplicate(rule))
			{
				throw new ArgumentException(string.Format("An equivalent rule already exists in collection."), "rule");
			}
		}

        private void CheckCanBeAppliedToType(Rule rule)
		{
			var targetType = Type.GetTypeFromHandle(targetTypeHandle);
			if (!rule.CanBeAppliedToType(targetType))
			{
				// throw an exception
				const string invalidTypeFormat = "Rule cannot be applied to the following type: '{0}'";
				var exceptionMessage = string.Format(invalidTypeFormat, targetType.ToUserFriendlyString());
				throw new ArgumentException(exceptionMessage, "value");
			}
		}

        private bool IsDuplicate(Rule rule)
		{
			// cached so we don't have to use reflection in the loop
			var ruleType = rule.GetType();

			var matchingRule = ruleList.FirstOrDefault(x => AreDuplicates(ruleType, rule, x));
			
			if (matchingRule != null)
				return true;
			else
				return false;
		}

		internal bool AreDuplicates(Type ruleToTestType, Rule ruleToTest, Rule existingRule)
		{
			if (ruleToTest == existingRule || ruleToTestType == existingRule.GetType())
			{
				if (existingRule.IsEquivalent(ruleToTest))
					return true;
			}
			return false;
		}

        public Rule this[int index]
		{
			get
			{
				return ruleList[index];
			}
		}
		/// <inheritdoc/>
        public IEnumerator<Rule> GetEnumerator()
		{
			return ruleList.GetEnumerator();
		}
		/// <inheritdoc/>
		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}
	}
}